package com.homewaimai.framework.acl;

import java.io.ByteArrayInputStream;
import java.io.FileReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ralasafe.EntityExistException;
import org.ralasafe.Factory;
import org.ralasafe.RalasafeException;
import org.ralasafe.application.Application;
import org.ralasafe.application.ApplicationUserType;
import org.ralasafe.db.DBLevelException;
import org.ralasafe.db.DBPower;
import org.ralasafe.db.FieldWhereElement;
import org.ralasafe.db.JavaBeanColumnAdapter;
import org.ralasafe.db.JavaBeanObjectNewer;
import org.ralasafe.db.SingleValueComparator;
import org.ralasafe.db.Table;
import org.ralasafe.db.TableNewer;
import org.ralasafe.db.TableUpdator;
import org.ralasafe.db.impl.TableDeletorImpl;
import org.ralasafe.db.impl.TableSaverImpl;
import org.ralasafe.db.impl.TableSelectorImpl;
import org.ralasafe.db.impl.TableUpdatorImpl;
import org.ralasafe.metadata.user.UserMetadata;
import org.ralasafe.metadata.user.UserMetadataParser;
import org.ralasafe.userType.UserType;
import org.ralasafe.userType.UserTypeManager;
import org.ralasafe.util.DBUtil;
import org.ralasafe.util.StringUtil;

public class UserTypeManagerImpl implements UserTypeManager {

    private static Log        logger = LogFactory.getLog(UserTypeManagerImpl.class);

    /**
     * UserType cache store
     */
    private Table             table;
    private TableSelectorImpl selector;
    private TableSaverImpl    saver;
    private TableUpdator      updator;
    private TableDeletorImpl  deletor;
    private FieldWhereElement nameWhereEmt;

    private TableDeletorImpl  applicationUserTypeDeletor;
    private FieldWhereElement userTypeNameAppUserTypeTableWhereEmt;

    public UserTypeManagerImpl(){
        initUserTypeTableInfo();
        initApplicationUserTypeTableInfo();
    }

    private void initApplicationUserTypeTableInfo() {
        TableNewer newer = new TableNewer();
        newer.setTableName("applicationusertype");
        newer.setColumnNames(new String[] { "appName", "userTypeName", "userMetadataStr" });
        newer.setIdColumnNames(new String[] { "appName", "userTypeName" });
        // newer.setUniqueColumnNames(new String[] { "name" });
        newer.setMappingClass(ApplicationUserType.class.getName());
        newer.put("appName", new JavaBeanColumnAdapter("appName", "java.lang.String"));
        newer.put("userTypeName", new JavaBeanColumnAdapter("userTypeName", "java.lang.String"));
        newer.put("userMetadataStr", new JavaBeanColumnAdapter("userMetadataStr", "java.lang.String"));

        newer.setId(DBPower.getTableId(null, newer.getTableName()));
        Table appUserTypeTable = newer.getTable();
        applicationUserTypeDeletor = new TableDeletorImpl();
        applicationUserTypeDeletor.setTable(appUserTypeTable);

        userTypeNameAppUserTypeTableWhereEmt = new FieldWhereElement();
        userTypeNameAppUserTypeTableWhereEmt.setColumn(appUserTypeTable.getColumns()[1]);
        userTypeNameAppUserTypeTableWhereEmt.setCompartor(SingleValueComparator.EQUAL);
        userTypeNameAppUserTypeTableWhereEmt.setContextValue(true);
    }

    private void initUserTypeTableInfo() {
        TableNewer newer = new TableNewer();
        newer.setTableName("usertype");
        newer.setColumnNames(new String[] { "name", "description", "userMetadataXML" });
        newer.setIdColumnNames(new String[] { "name" });
        // newer.setUniqueColumnNames(new String[] { "name" });
        newer.setMappingClass(UserType.class.getName());
        newer.put("name", new JavaBeanColumnAdapter("name", "java.lang.String"));
        newer.put("description", new JavaBeanColumnAdapter("desc", "java.lang.String"));
        newer.put("userMetadataXML", new JavaBeanColumnAdapter("userMetadataXML", "java.lang.String"));

        newer.setId(DBPower.getTableId(null, newer.getTableName()));
        table = newer.getTable();
        selector = new TableSelectorImpl();
        selector.setObjectNewer(new JavaBeanObjectNewer(newer.getMappingClass()));
        saver = new TableSaverImpl();
        updator = new TableUpdatorImpl();
        deletor = new TableDeletorImpl();
        selector.setTable(table);
        saver.setTable(table);
        updator.setTable(table);
        deletor.setTable(table);

        nameWhereEmt = new FieldWhereElement();
        nameWhereEmt.setColumn(table.getColumns()[0]);
        nameWhereEmt.setCompartor(SingleValueComparator.EQUAL);
        nameWhereEmt.setContextValue(true);
    }

    private Map loadIntoMemory() {
        Collection allUserTypes = selector.select(null, null);
        Map userTypeMap = new HashMap();
        for (Iterator iter = allUserTypes.iterator(); iter.hasNext();) {
            UserType userType = (UserType) iter.next();
            extractUserMetadata(userType);
            userTypeMap.put(userType.getName(), userType);
        }
        return userTypeMap;
    }

    private void preModify(UserType userType) {
        String xml = getUserMetadataXML(userType);
        if (xml.length() >= 1000 && xml.length() <= 2000) {
            xml = StringUtils.rightPad(xml, 4000 - xml.length());
        }

        userType.setUserMetadataXML(xml);
    }

    public void addUserType(UserType userType) throws EntityExistException {
        Map userTypeMap = loadIntoMemory();
        // check duplicated name
        if (userTypeMap.keySet().contains(userType.getName())) {
            throw new EntityExistException("The name '" + userType.getName() + "' exists already.");
        }

        preModify(userType);

        extractUserMetadata(userType);

        saver.save(userType);
    }

    private String getUserMetadataXML(UserType userType) {
        int ch = 0;
        StringBuffer buf = new StringBuffer();
        try {
            FileReader reader = new FileReader(userType.getSrcFile());
            while ((ch = reader.read()) != -1) {
                buf.append((char) ch);
            }
            reader.close();
            return buf.toString();
        } catch (Exception e) {
            throw new RalasafeException(e);
        }
    }

    public void deleteUserType(String name) {
        loadIntoMemory();

        Collection applications = getApplications(name);

        Connection conn = null;
        boolean autoCommit = true;
        try {
            conn = DBPower.getConnection(table.getId());
            autoCommit = conn.getAutoCommit();
            conn.setAutoCommit(false);

            // drop application's user-role tables
            for (Iterator itr = applications.iterator(); itr.hasNext();) {
                Application application = (Application) itr.next();
                String dropSql = DBUtil.userRoleTableDropSql(application.getName(), name);
                try {
                    DBUtil.exec(conn, dropSql);
                } catch (SQLException e) {
                    logger.error("Failed to delete table: " + dropSql);
                }
            }

            // delete app-usertype relationship
            ApplicationUserType appUserTypeHint = new ApplicationUserType();
            appUserTypeHint.setUserTypeName(name);
            applicationUserTypeDeletor.delete(conn, userTypeNameAppUserTypeTableWhereEmt, appUserTypeHint);

            // delete UserType
            UserType userTypeHint = new UserType();
            userTypeHint.setName(name);
            deletor.deleteByIdColumns(conn, userTypeHint);

            conn.commit();
        } catch (SQLException e) {
            DBUtil.rollback(conn);
            throw new DBLevelException(e);
        } finally {
            DBUtil.setCommitMode(conn, autoCommit);
            DBUtil.close(conn);
        }

        // userTypeMap.remove(name);
        // notify Factory
        Factory.userTypeChanged(name);
    }

    public Collection getAllUserTypes() {
        Map userTypeMap = loadIntoMemory();

        return userTypeMap.values();
    }

    public UserType getUserType(String name) {
        Map userTypeMap = loadIntoMemory();
        return (UserType) userTypeMap.get(name);
    }

    /**
     * Extract UserMetadata from userType's xml definition, and validate the infomation
     * 
     * @param userType
     */
    private void extractUserMetadata(UserType userType) {
        // parse
        if (userType.getUserMetadataXML() != null) {
            UserMetadataParser parser = new UserMetadataParser();
            UserMetadata userMetadata = parser.parse(new ByteArrayInputStream(userType.getUserMetadataXML().getBytes()));

            String validInfo = userMetadata.getValidInfo();
            if (!StringUtil.isEmpty(validInfo)) {
                System.out.println("Error: user metadata is invalid! name=" + userType.getName());
                System.out.println(validInfo);
                System.out.println("userMetadataStr=");
                System.out.println(userType.getUserMetadataXML());
                throw new RalasafeException("User metadata is invalid! " + validInfo);
            }

            userType.setUserMetadata(userMetadata);
        }
    }

    public void updateUserType(UserType userType) {
        preModify(userType);

        extractUserMetadata(userType);

        try {
            updator.updateByIdColumns(userType);
        } catch (EntityExistException e) {
            throw new DBLevelException(e);
        }
        // notify Factory
        Factory.userTypeChanged(userType.getName());
    }

    public Collection getApplications(String userTypeName) {
        loadIntoMemory();

        Set applications = new HashSet();

        Iterator appItr = Factory.getApplicationManager().getAllApplications().iterator();
        while (appItr.hasNext()) {
            Application application = (Application) appItr.next();
            Iterator itr = application.getUserTypes().iterator();
            while (itr.hasNext()) {
                UserType userType = (UserType) itr.next();
                if (userType.getName().equals(userTypeName)) applications.add(application);
            }
        }
        return applications;
    }

    public UserType getUserTypeCopy(String name) {
        Map userTypeMap = loadIntoMemory();
        UserType cacheOne = (UserType) userTypeMap.get(name);
        if (cacheOne == null) {
            return null;
        } else {
            UserType copy = new UserType();
            copy.setDesc(cacheOne.getDesc());
            copy.setName(cacheOne.getName());
            copy.setSrcFile(cacheOne.getSrcFile());
            copy.setUserMetadataXML(cacheOne.getUserMetadataXML());
            extractUserMetadata(copy);
            return copy;
        }
    }

}
